﻿using Extented.UI.Core.Helpers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Input;

namespace Extented.UI.Core.Native
{
    public static class RenderEventManager
    {
#if DEBUGTEST
		static Point? DEBUGTEST_MousePosition;
		internal static void DEBUGTEST_SetMousePosition(Point p) {
			DEBUGTEST_MousePosition = p;
		}
		internal static void DEBUGTEST_ResetMousePosition() {
			DEBUGTEST_MousePosition = null;
		}
#endif
        public static void PropagateEvent(this Chrome chrome, RoutedEventArgs args, RenderEvents renderEvent)
        {
            if (chrome.Root == null)
                return;
            if (((int)RenderEventKinds.Mouse & (int)renderEvent) != 0)
            {
                PropagateMouseEvent(chrome, (MouseEventArgs)args, renderEvent);
            }
            IEnumerable<FrameworkRenderElementContext> route = RenderTreeHelper.RenderDescendants(chrome.Root);
            var renderArgs = new RenderEventArgs(chrome.Root, args, renderEvent);
            if (args.RoutedEvent.RoutingStrategy == RoutingStrategy.Bubble)
                route = route.Reverse();
            RaiseEvent(renderArgs, route);
        }
        static void PropagateMouseEvent(Chrome chrome, MouseEventArgs args, RenderEvents renderEvent)
        {
            FrameworkRenderElementContext[] route = null;
            FrameworkRenderElementContext[] mouseEnterRoute = null;
            FrameworkRenderElementContext[] mouseLeaveRoute = null;
            var source = chrome.Root;
            Point mousePosition = ((MouseEventArgs)args).GetPosition(chrome);
            bool isMouseOver = chrome.IsMouseOver;
#if DEBUGTEST
			if (DEBUGTEST_MousePosition.HasValue) {
				mousePosition = DEBUGTEST_MousePosition.Value;
				isMouseOver = true;
			}				
#endif
            route = GetDescendantsUnderMouse(source, mousePosition, out mouseEnterRoute, out mouseLeaveRoute, isMouseOver);
            if (args.RoutedEvent.RoutingStrategy == RoutingStrategy.Bubble)
                route = route.Reverse().ToArray();
            if (mouseEnterRoute != null)
            {
                RaiseEvent(new MouseRenderEventArgs(source, args, RenderEvents.MouseEnter, mousePosition), mouseEnterRoute);
            }
            if (mouseLeaveRoute != null)
            {
                RaiseEvent(new MouseRenderEventArgs(source, args, RenderEvents.MouseLeave, mousePosition), mouseLeaveRoute);
            }
            if (renderEvent != RenderEvents.MouseLeave && renderEvent != RenderEvents.MouseEnter)
                RaiseEvent(new MouseRenderEventArgs(source, args, renderEvent, mousePosition), route);
        }
        static FrameworkRenderElementContext[] GetDescendantsUnderMouse(FrameworkRenderElementContext context, Point relativePoint, out FrameworkRenderElementContext[] addedElements, out FrameworkRenderElementContext[] removedElements, bool actualIsMouseOver)
        {
            List<FrameworkRenderElementContext> result = new List<FrameworkRenderElementContext>();
            if (actualIsMouseOver)
                RenderTreeHelper.HitTest(context, frec => RenderHitTestFilterBehavior.Continue, frec => { result.Add(frec); return RenderHitTestResultBehavior.Continue; }, relativePoint);
            addedElements = result.Where(x => x.IsMouseOverCore == false).ToArray();
            removedElements = RenderTreeHelper.RenderDescendants(context).Concat(new FrameworkRenderElementContext[] { context }).Except(result).Where(x => x.IsMouseOverCore == true).ToArray();
            return result.ToArray();
        }
        public static void RaiseEvent(RenderEventArgsBase args, IEnumerable<IFrameworkRenderElementContext> eventRoute)
        {
            if (args.Handled)
                return;
            foreach (var element in eventRoute)
            {
                args.InvokeEventHandler(element);
                if (args.Handled && args.With(x => x.OriginalEventArgs as RoutedEventArgs).With(x => x.RoutedEvent).If(x => x.RoutingStrategy == RoutingStrategy.Direct) == null) break;
            }
            if (args.Handled)
                (args.OriginalEventArgs as RoutedEventArgs).Do(x => x.Handled = true);
        }
    }
}
